/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pl.butler.cezary.syncTool.storage;

import pl.butler.cezary.syncTool.storage.metadata.FileMetadata;
import pl.butler.cezary.syncTool.storage.metadata.FileVersionMetadata;
import java.io.File;
import java.io.FileInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.codec.binary.Base32;
import pl.butler.cezary.syncTool.scanning.ChangeStatus;
import pl.butler.cezary.syncTool.storage.locks.FileLock;
import pl.butler.cezary.syncTool.storage.locks.IStorageLock;
import pl.butler.cezary.syncTool.storage.metadata.IMetadataStorage;
import pl.butler.cezary.syncTool.storage.metadata.SerializedMetadataStorage;
import pl.butler.cezary.syncTool.storage.strategies.ISavingStrategy;
import pl.butler.cezary.syncTool.storage.strategies.OnceATimeStrategy;

/**
 * NOT THREAD SAFE:
 * problems:
 * - B32codec
 * - message digest
 * @author czarek
 */
public abstract class Storage {

    private static enum State {

        NEW, OPEN, CLOSED
    };
    private static final Logger log = Logger.getLogger(Storage.class.getSimpleName());
    private MagazynId magazyn;
    private ISavingStrategy saveStrat = new OnceATimeStrategy(50);
    private IStorageLock lock;
    private IMetadataStorage metaStorage;
    private State state = State.NEW;

    protected Storage(MagazynId magazyn) {
        this.magazyn = magazyn;
        lock = new FileLock(new File(magazyn.getSciezka(), ".~slock"));
        //metaStorage = new SerializedMetadataStorage(META)
        Runtime.getRuntime().addShutdownHook(new Thread(new Finalizer()));

    }

    public MagazynId getMagazyn() {
        return magazyn;
    }

    protected abstract IMetadataStorage createMetadataStorage();

    public FileMetadata getFileMetadata(File plik) {
        return metaStorage.getFileMetadata(plik);
    }

    public FileVersionMetadata getCurrentFileVersionMetadata(File plik) {
        final FileMetadata fileMetadata = getFileMetadata(plik);
        if (fileMetadata != null) {
            return fileMetadata.getCurrentVersion();
        }
        return null;
    }

    public void storeChange(File file, ChangeStatus status) {
        metaStorage.registerChange(file, status);
        /*final String checksum = calclulateChecksum(file);
        final String path = file.getPath();
        final FileVersionMetadata newVersionMetadata = new FileVersionMetadata(file, checksum);
        if(status == ChangeStatus.NEW){
        metadataMap.put(path, new FileMetadata(newVersionMetadata, file));
        }else{
        final FileMetadata meta=metadataMap.get(path);
        if(meta!=null){
        //FIXME czy tutaj powinna być implementacja czy może w metadataStorage
        }else{
        throw new RuntimeException("Wykryta zmiana sugeruje że plik powinien znajdować się w magazynie, jednak nie znaleziono go tam");
        }               
        }       */
        saveFile(file);
    }

    public void open() {
        if (log.isLoggable(Level.FINE)) {
            log.fine("Otwieranie pliku magazynu");
        }
        if (!lock.lock()) {
            log.warning("Nie udało się założyć blokady, spójność magazynu jest zagrożona");
        }
        metaStorage = createMetadataStorage();
        loadData();
        state = State.OPEN; //kontrola w state?
    }

    public void close() {
        if (state == State.OPEN) {
            if (log.isLoggable(Level.FINE)) {
                log.fine("Zamykanie pliku magazynu");
            }
            saveData();
            if (!lock.release()) {
                log.warning("Nie udało się zdjąć blokady, spójność magazynu jest zagrożona");
            }
            state = State.CLOSED;
        } else {
            log.info("Magazyn został już zamknięty, nie ma potrzeby robić tego ponownie");
        }

    }

    public void suggestSave() {
        if (log.isLoggable(Level.FINE)) {
            log.fine("Zaproponowano zapisanie danych");
        }
        if (saveStrat.doSave()) {
            saveData();
        }
    }

    //TODO warto zastanowić się jak będzie z magazynami gdzie pliki i metadane są trzymane razem, w jednym archiwum
    protected void saveData() {
        metaStorage.save();
    }

    protected void loadData() {
        metaStorage.load();
    }

    public abstract void saveFile(File plik);

    public abstract void restoreFile(File plik);

    private class Finalizer implements Runnable {

        public void run() {
            log.info("Awaryjne zamykanie magazynu");
            close();
            log.info("Zakończono poprawnie");
        }
    }
}
